﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Build.Construction;
using Devtm.Common;
namespace Devtm.Generator
{


    public enum EnumType
    {
        NotSpecified,
        Code,
        Content,
        Resource,
        Xml,
        EmbeddedResource,
    }


    [System.Diagnostics.DebuggerDisplay("{ProjectFile.Name}")]
    public class Project
    {

        public FileInfo ProjectFile { get; set; }
        private string rootPath;
        private ProjectRootElement project;

        private const string ProjectConst2 = "Project";
        private const string ProjectConst = "project";
        private const string PathConst = "path";
        private const string Compile = "Compile";
        private const string Reference = "Reference";
        private const string ProjectReference = "ProjectReference";
        private const string Content = "Content";
        private const string EmbeddedResource = "EmbeddedResource";
        private const string Resource = "Resource";
        private const string ClassName = "classname";
        private bool updated;



        public Project(FileInfo file)
        {

            if (!file.Exists)
                throw new FileNotFoundException(file.FullName);

            this.ProjectFile = file;
            rootPath = file.Directory.FullName;
            project = ProjectRootElement.Open(file.FullName);
            RootNamespace = GetValue(project.Properties.Where(c => c.Name == "RootNamespace").FirstOrDefault());
            AssemblyName = GetValue(project.Properties.Where(c => c.Name == "AssemblyName").FirstOrDefault());
            ProjectGuid = GetValue(project.Properties.Where(c => c.Name == "ProjectGuid").FirstOrDefault());
            OutputType = GetValue(project.Properties.Where(c => c.Name == "OutputType").FirstOrDefault());


            //AssemblyName
            string p1 = System.IO.Path.Combine(project.DirectoryPath, rootPath);

            switch (file.Extension)
            {

                case ".csproj":
                    this.Extension = ".cs";
                    break;

                case ".vbproj":
                    this.Extension = ".vb";
                    break;

                default:
                    this.Extension = ".cs";
                    break;

            }

            Types = new HashSet<string>();

        }

        private string GetValue(ProjectPropertyElement e)
        {
            if (e != null)
                return e.Value;

            return string.Empty;
        }



        public void AddFile(FileInfo item, EnumType content, string @namespace)
        {

            string typeItem = string.Empty;
            var metadata = new List<KeyValuePair<string, string>>();
            string include = string.Concat(@namespace, @"\", item.Name);

            switch (content)
            {
                case EnumType.EmbeddedResource:
                    typeItem = EmbeddedResource;
                    metadata.Add(new KeyValuePair<string, string>("Generator", "ResXFileCodeGenerator"));
                    break;
                case EnumType.Resource:
                    typeItem = Resource;
                    break;
                case EnumType.Code:
                    typeItem = Compile;
                    break;
                case EnumType.Xml:
                case EnumType.Content:
                    typeItem = Content;
                    break;
            }


            // Liste des fichiers referencés dans le projet.
            var l1 = new HashSet<string>(project.Items.Where(c => c.ItemType == typeItem & c.Include.StartsWith(include))
                                                      .Select(d => Path.Combine(project.DirectoryPath, d.Include)));


            if (!l1.Contains(item.FullName))                
            {
                
                if (metadata.Count > 0)
                    project.AddItem(typeItem, include, metadata);
                else
                    project.AddItem(typeItem, include);

                updated = true;
                this.Log(LogLevelEnm.Trace, "item Added: {0}", include, Tags.RemovedFromProject);

            }


        }

        public void RemoveFile(FileInfo item, EnumType content, string @namespace)
        {

            string typeItem = string.Empty;
            string include = string.Concat(@namespace, @"\", item.Name);

            switch (content)
            {
                case EnumType.Code:
                    typeItem = Compile;
                    break;
                case EnumType.Content:
                    typeItem = Content;
                    break;
                case EnumType.Resource:
                    typeItem = EmbeddedResource;
                    break;
            }

            // Liste des fichiers referencés dans le projet.
            var l1 = new HashSet<string>(project.Items.Where(c => c.ItemType == Compile & c.Include.StartsWith(include))
                                                      .Select(d => Path.Combine(project.DirectoryPath, d.Include)));


            if (l1.Contains(item.FullName))
            {
                var l2 = project.Items.First(c => c.ItemType == Compile & c.Include.StartsWith(include));
                if (l2 != null)
                {
                    l2.Parent.RemoveChild(l2);
                    updated = true;

                    this.Log(LogLevelEnm.Trace, "item {0} removed", include, Tags.RemovedFromProject);

                }
            }


        }

        public void Save()
        {
            if (updated)
            {
                project.Save();
                updated = false;

                this.Log(LogLevelEnm.Trace, "Project '{0}' saved", AssemblyName, Tags.ProjectSaved);

            }
        }

        public string GetFilename(string @namespace, string className, string extendFilename = "")
        {

            string r = ProjectFile.Directory.FullName;

            if (!string.IsNullOrEmpty(@namespace) && !string.IsNullOrEmpty(@namespace.Trim('\\')))
                r = Path.Combine(r, @namespace);

            r = Path.Combine(r, className);

            if (!string.IsNullOrEmpty(extendFilename))
            {
                if (!extendFilename.StartsWith("."))
                    extendFilename = "." + extendFilename;
                r += extendFilename;
            }

            return r;

        }





        public string GetValidTypeName(string p)
        {

            return p[0].ToString().ToUpper() + p.Substring(1);

        }

        public string GetValidPropertyName(string p)
        {

            return p[0].ToString().ToUpper() + p.Substring(1);

        }



        #region Imports

        internal void ImportAssemblyNameReference(string assemblyName)
        {

            var lst = GetElements(Reference).ToList();

            ProjectItemGroupElement parent;

            bool create = false;
            if (lst.Count > 0)
            {
                parent = lst.First().Parent as ProjectItemGroupElement;
                var item = lst.Select(c => c.Include).FirstOrDefault(d => d.StartsWith(assemblyName));
                if (item == null)
                    create = true;
            }
            else
            {
                parent = this.project.AddItemGroup();
                create = true;
            }

            if (create)
            {
                var refval = parent.AddItem(Reference, assemblyName);
                /* <Reference Include="System" /> */

                updated = true;

                this.Log(LogLevelEnm.Trace, "Reference {0} Added", Reference, Tags.ReferenceAddedProject);

            }

        }

        internal void ImportByFilenameReference(string assemblyName, string location)
        {
            // a tester
            var lst = GetElements(Reference).ToList();

            ProjectItemGroupElement parent;

            bool create = false;
            if (lst.Count > 0)
            {
                parent = lst.First().Parent as ProjectItemGroupElement;
                var item = lst.Select(c => c.Include).FirstOrDefault(d => d.StartsWith(assemblyName));
                if (item == null)
                    create = true;
            }
            else
            {
                parent = this.project.AddItemGroup();
                create = true;
            }

            if (create)
            {
                System.Uri uri1 = new Uri(ProjectFile.FullName);
                System.Uri uri2 = new Uri(location);
                Uri relativeUri = uri1.MakeRelativeUri(uri2);
                string a = relativeUri.ToString().Replace("/", "\\");

                var refval = parent.AddItem(Reference, assemblyName);
                refval.AddMetadata("HintPath", a);

                updated = true;

                this.Log(LogLevelEnm.Trace, "Reference {0} Added", Reference, Tags.ReferenceAddedProject);

                /*
                 <Reference Include="Mono.Security">
                  <HintPath>..\ThirdParty\Npgsql\Mono.Security.dll</HintPath>
                </Reference>
                */

            }

        }

        internal void ImportByProjectReference(string projectName, string location)
        {

            Project prj = Solution.GetProjectByFilename(location);
            ImportByProjectReference(prj);
        }

        internal void ImportByProjectReference(Project project)
        {

            var lst = GetElements(ProjectReference).ToList();

            foreach (var item in lst)
            {
                FileInfo _f = ResolvePath(item.Include);
                if (_f.FullName == project.ProjectFile.FullName)
                    return;
            }

            ProjectItemGroupElement parent;
            if (lst.Count > 0)
                parent = lst.First().Parent as ProjectItemGroupElement;
            else
                parent = this.project.AddItemGroup();

            System.Uri uri1 = new Uri(ProjectFile.FullName);
            System.Uri uri2 = new Uri(project.ProjectFile.FullName);
            Uri relativeUri = uri1.MakeRelativeUri(uri2);
            string a = relativeUri.ToString().Replace("/", "\\");

            ProjectItemElement refProj = parent.AddItem(ProjectReference, a);
            refProj.AddMetadata(ProjectConst2, project.ProjectGuid);
            refProj.AddMetadata("Name", project.AssemblyName);

            updated = true;

            this.Log(LogLevelEnm.Trace, "Reference {0} Added", Reference, Tags.ReferenceAddedProject);

            /*<ItemGroup>
                <ProjectReference Include="..\NpgsqlMetadata\NpgsqlMetadata.csproj">
                  <Project>{D2BECCFD-F6D8-4862-85B6-DD4238F2736D}</Project>
                  <Name>NpgsqlMetadata</Name>
                </ProjectReference>
              </ItemGroup>*/

        }


        private FileInfo ResolvePath(string item)
        {
            var i = new Stack<string>(ProjectFile.Directory.FullName.Split('\\'));
            var j = item.Split('\\');
            foreach (var k in j)
            {
                if (k == ".")
                {

                }
                else if (k == "..")
                    i.Pop();
                else
                    i.Push(k);
            }

            string _p = string.Empty;
            foreach (var k in i.Reverse())
            {
                if (!string.IsNullOrEmpty(_p))
                    _p += "\\";
                _p += k;
            }

            return new FileInfo(_p);

        }

        #endregion




        private IEnumerable<Microsoft.Build.Construction.ProjectItemElement> GetElements(string name)
        {
            foreach (var item in project.ItemGroups)
                foreach (var item2 in item.Children)
                {
                    var t = item2 as Microsoft.Build.Construction.ProjectItemElement;
                    if (t != null)
                        if (t.ItemType == name)
                            yield return t;
                }
        }


        public Solution Solution { get; internal set; }
        public string AssemblyName { get; private set; }
        public HashSet<string> Types { get; private set; }
        public string Extension { get; private set; }
        public string RootNamespace { get; private set; }
        public string ProjectGuid { get; set; }
        public string OutputType { get; set; }
    }


}



